﻿//==============================================================================  
//Copyright (C) 2012-2015 9UN.ORG. All rights reserved. 
//GUID：46172595-6b69-4526-b9da-426d663f5b05
//CLR Version: 4.0.30319.18033
//Code Author：Kevin Wang
//Contact：Email(Admin@9un.org),QQ(265382 or 74344)
//Filename：DoubleWindowBase
//Namespace：RuanRang.Media.Core
//Functions：DoubleWindowBase  
//Created by Kevin Wang at 2013/2/2 23:05:09 http://blog.9un.org
//============================================================================== 

#region Usings

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using RuanRang.Media.Core;

#endregion

namespace RuanRang.Media.WinForms
{
    /// <summary>
    /// Represents a window based on two WinForms Panels.
    /// </summary>
    public sealed class PanelledDoubleWindow : DoubleWindowBase
    {
        private readonly Thread _thread;
        private readonly bool _useParentWindowsFormsContext;
        private readonly WindowsFormsThreadTask _windowsFormsThreadTask;
        private string _backgroundImageFilePath = String.Empty;

        private bool _firstWindowIsActive = true;
        private bool _playerVisible = true;
        private bool _visible;

        #region Nested "private sealed class WindowsFormsThreadTask"

        #region Nested type: FormInvokerDelegate

        /// <summary>
        /// Delegate FormInvokerDelegate
        /// </summary>
        private delegate void FormInvokerDelegate();

        /// <summary>
        /// Delegate FormInvokerDelegate
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        private delegate void FormInvokerDelegate<in T>(T value);

        #endregion

        #region Nested type: WindowsFormsThreadTask

        private sealed class WindowsFormsThreadTask
        {
            /// <summary>
            /// The _handle created event
            /// </summary>
            private readonly EventWaitHandle _handleCreatedEvent = new EventWaitHandle(false, EventResetMode.AutoReset);
            internal Form WindowsForm;
            internal Panel Panel1;

            private bool _panel1Created;
            private bool _panel2Created;

            internal IntPtr Panel1Handle;
            internal Panel Panel2;

            internal IntPtr Panel2Handle;

            /// <summary>
            /// Internals the create form.
            /// </summary>
            internal void InternalCreateForm()
            {
                WindowsForm = new Form
                                  {
                                      FormBorderStyle = FormBorderStyle.None,
                                      StartPosition = FormStartPosition.WindowsDefaultLocation,
                                      Location = new Point(0, 0),
                                      Visible = false,
                                      TopMost = true,
                                      ShowInTaskbar = false
                                  };
                // Important : HandleCreated event can be raised only after Visible will be set to true
                // (http://msdn.microsoft.com/en-us/library/system.windows.forms.control.handlecreated.aspx)
                //
                if (!WindowsForm.IsHandleCreated)
                {
                    WindowsForm.HandleCreated += internalFormHandleCreatedHandler;
                }
                else
                {
                    internalFormHandleCreatedHandler(this, EventArgs.Empty);
                }
                WindowsForm.Visible = true;
            }

            /// <summary>
            /// Windowses the forms thread main.
            /// </summary>
            [STAThread]
            public void WindowsFormsThreadMain()
            {
                InternalCreateForm();
                //
                Application.Run(WindowsForm);
            }

            /// <summary>
            /// Internals the form handle created handler.
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
            private void internalFormHandleCreatedHandler(object sender, EventArgs e)
            {
                WindowsForm.MouseEnter += MouseEnterHandler;
                WindowsForm.MouseLeave += MouseLeaveHandler;
                //
                Panel1 = new Panel
                             {
                                 Parent = WindowsForm,
                                 Location = new Point(0, 0),
                                 Width = WindowsForm.Width,
                                 Height = WindowsForm.Height,
                                 Visible = false
                             };
                //
                if (!Panel1.IsHandleCreated)
                {
                    Panel1.HandleCreated += InternalPanel1HandleCreated;
                }
                else
                {
                    InternalPanel1HandleCreated(this, EventArgs.Empty);
                }
                Panel1.Visible = true;
                //
                Panel2 = new Panel
                             {
                                 Parent = WindowsForm,
                                 Location = new Point(0, 0),
                                 Width = WindowsForm.Width,
                                 Height = WindowsForm.Height,
                                 Visible = false
                             };
                //
                if (!Panel2.IsHandleCreated)
                {
                    Panel2.HandleCreated += InternalPanel2HandleCreated;
                }
                else
                {
                    InternalPanel2HandleCreated(this, EventArgs.Empty);
                }
                Panel2.Visible = true;
            }

            /// <summary>
            /// Internals the panel1 handle created.
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
            private void InternalPanel1HandleCreated(object sender, EventArgs e)
            {
                Panel1Handle = Panel1.Handle;
                //
                Panel1.MouseEnter += MouseEnterHandler;
                Panel1.MouseLeave += MouseLeaveHandler;
                //
                _panel1Created = true;
                //
                if (_panel1Created && _panel2Created)
                {
                    _handleCreatedEvent.Set();
                }
            }

            /// <summary>
            /// Internals the panel2 handle created.
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
            private void InternalPanel2HandleCreated(object sender, EventArgs e)
            {
                Panel2Handle = Panel2.Handle;
                //
                Panel2.MouseEnter += MouseEnterHandler;
                Panel2.MouseLeave += MouseLeaveHandler;
                //
                _panel2Created = true;
                //
                if (_panel1Created && _panel2Created)
                {
                    _handleCreatedEvent.Set();
                }
            }

            /// <summary>
            /// Mouses the enter handler.
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
            private static void MouseEnterHandler(object sender, EventArgs e)
            {
                Cursor.Hide();
            }

            /// <summary>
            /// Mouses the leave handler.
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
            private static void MouseLeaveHandler(object sender, EventArgs e)
            {
                Cursor.Show();
            }

            /// <summary>
            /// Waits for windows creation.
            /// </summary>
            /// <param name="timeout">The timeout.</param>
            /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
            public bool WaitForWindowsCreation(TimeSpan timeout)
            {
                while (!_panel1Created && !_panel2Created)
                {
                    if (!_handleCreatedEvent.WaitOne(timeout, false))
                    {
                        return (false);
                    }
                }
                return (true);
            }
        }

        #endregion

        #endregion

        #region Constructors & Destructors

        private bool _isDisposed;

        /// <summary>
        /// Creates a window. Visible = false by default, and creates a separate thread to manage Windows messages.
        /// </summary>
        public PanelledDoubleWindow() :
            this(TimeSpan.FromSeconds(2), true)
        {
        }

        /// <summary>
        /// Creates a window. Visible = false by default.
        /// </summary>
        /// <param name="operationTimeout">Timeout to waiting a window creation.</param>
        /// <param name="useParentWindowsFormsContext">Creates a separate thread with Application.Run() if false.</param>
        /// <exception cref="System.InvalidOperationException">Cannot create window handles.</exception>
        public PanelledDoubleWindow(TimeSpan operationTimeout, bool useParentWindowsFormsContext)
        {
            _useParentWindowsFormsContext = useParentWindowsFormsContext;
            //
            if (useParentWindowsFormsContext)
            {
                _windowsFormsThreadTask = new WindowsFormsThreadTask();
                _windowsFormsThreadTask.InternalCreateForm();
            }
            else
            {
                //
                // Starting a new message dispatcher thread.
                // After that we have to call form.Invoke() to access its properties.
                _windowsFormsThreadTask = new WindowsFormsThreadTask();
                _thread = new Thread(_windowsFormsThreadTask.WindowsFormsThreadMain) {IsBackground = false};

                _thread.SetApartmentState(ApartmentState.STA);
                _thread.Start();
            }

            if (!_windowsFormsThreadTask.WaitForWindowsCreation(operationTimeout))
            {
                Dispose(false);
                throw new InvalidOperationException("Cannot create window handles.");
            }
            //
            // Initial visibility.
            _windowsFormsThreadTask.WindowsForm.Invoke(new FormInvokerDelegate(FormInvokeDelegate));
        }

        /// <summary>
        /// Forms the invoke delegate.
        /// </summary>
        private void FormInvokeDelegate()
        {
            _windowsFormsThreadTask.WindowsForm.Visible = _visible;
            GETActivePanel().Visible = true;
            GETInactivePanel().Visible = false;
        }

        /// <summary>
        /// Cleanup resourcs.
        /// </summary>
        /// <param name="isDisposing">Defines where this method is invoked. <code>True</code> if from Dispose call, else from finalizer.</param>
        /// <exception cref="System.InvalidOperationException">Thread is already stopped.</exception>
        protected override void Dispose(bool isDisposing)
        {
            if (_isDisposed)
                return;
            //
            _isDisposed = true;
            if (!isDisposing) return;
            if (!_useParentWindowsFormsContext)
            {
                if (!_thread.IsAlive)
                {
                    throw new InvalidOperationException("Thread is already stopped.");
                }
            }
            //
            _windowsFormsThreadTask.WindowsForm.Invoke(new FormInvokerDelegate(closeHandler));
            //
            if (!_useParentWindowsFormsContext)
            {
                _thread.Join();
            }
            //
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Closes the handler.
        /// </summary>
        private void closeHandler()
        {
            _windowsFormsThreadTask.WindowsForm.Close();
        }

        /// <summary>
        /// TODO : Refactor this !
        /// Finalizer.
        /// </summary>
        ~PanelledDoubleWindow()
        {
            Dispose(false);
        }

        #endregion

        /// <summary>
        /// GETs the active panel.
        /// </summary>
        /// <returns>Panel.</returns>
        private Panel GETActivePanel()
        {
            return _firstWindowIsActive ? _windowsFormsThreadTask.Panel1 : _windowsFormsThreadTask.Panel2;
        }

        /// <summary>
        /// GETs the inactive panel.
        /// </summary>
        /// <returns>Panel.</returns>
        private Panel GETInactivePanel()
        {
            return _firstWindowIsActive ? _windowsFormsThreadTask.Panel2 : _windowsFormsThreadTask.Panel1;
        }

        /// <summary>
        /// Window width.
        /// </summary>
        public override int Width
        {
            get
            {
                return _windowsFormsThreadTask.WindowsForm.Width;
            }
            set
            {
                _windowsFormsThreadTask.WindowsForm.Invoke(new FormInvokerDelegate<int>(setWidthHandler), value);
            }
        }

        /// <summary>
        /// Window height.
        /// </summary>
        public override int Height
        {
            get
            {
                return _windowsFormsThreadTask.WindowsForm.Height;
            }
            set
            {
                _windowsFormsThreadTask.WindowsForm.Invoke(new FormInvokerDelegate<int>(setHeightHandler), value);
            }
        }

        private void setWidthHandler(int value)
        {
            _windowsFormsThreadTask.WindowsForm.Width = value;
            _windowsFormsThreadTask.Panel1.Width = value;
            _windowsFormsThreadTask.Panel2.Width = value;
        }

        private void setHeightHandler(int value)
        {
            _windowsFormsThreadTask.WindowsForm.Height = value;
            _windowsFormsThreadTask.Panel1.Height = value;
            _windowsFormsThreadTask.Panel2.Height = value;
        }

        private void setLeftHandler(int value)
        {
            _windowsFormsThreadTask.WindowsForm.Left = value;
        }

        /// <summary>
        /// Window left position.
        /// </summary>
        public override int Left
        {
            get
            {
                return _windowsFormsThreadTask.WindowsForm.Left;
            }
            set
            {
                _windowsFormsThreadTask.WindowsForm.Invoke(new FormInvokerDelegate<int>(setLeftHandler), value);
            }
        }

        /// <summary>
        /// Window top position.
        /// </summary>
        /// <value>The top.</value>
        public override int Top
        {
            get
            {
                return _windowsFormsThreadTask.WindowsForm.Top;
            }
            set
            {
                _windowsFormsThreadTask.WindowsForm.Invoke(new FormInvokerDelegate<int>(setTopHandler), value);
            }
        }

        /// <summary>
        /// Sets the top handler.
        /// </summary>
        /// <param name="value">The value.</param>
        private void setTopHandler(int value)
        {
            _windowsFormsThreadTask.WindowsForm.Top = value;
        }

        private uint _backColor;

        /// <summary>
        /// Background color code.
        /// </summary>
        /// <value>The color of the background.</value>
        public override uint BackgroundColor
        {
            get
            {
                return unchecked((uint)_windowsFormsThreadTask.WindowsForm.BackColor.ToArgb());
            }
            set
            {
                _backColor = value;
                _windowsFormsThreadTask.WindowsForm.Invoke(new FormInvokerDelegate(setBackgroundColorHandler));
            }
        }

        /// <summary>
        /// Sets the background color handler.
        /// </summary>
        private void setBackgroundColorHandler()
        {
            _windowsFormsThreadTask.WindowsForm.BackColor = Color.FromArgb(unchecked((int)_backColor));
        }

        /// <summary>
        /// Background image for window.
        /// </summary>
        public override string BackgroundImageFilePath
        {
            get
            {
                return _backgroundImageFilePath;
            }
            set
            {
                if (_backgroundImageFilePath != value)
                {
                    _backgroundImageFilePath = value;
                    _windowsFormsThreadTask.WindowsForm.Invoke(
                        new FormInvokerDelegate(setBackgroundImageFilePathHandler));
                }
            }
        }

        /// <summary>
        /// Sets the background image file path handler.
        /// </summary>
        private void setBackgroundImageFilePathHandler()
        {
            _windowsFormsThreadTask.WindowsForm.BackgroundImage = new Bitmap(_backgroundImageFilePath);
        }

        /// <summary>
        /// Is window visible.
        /// </summary>
        /// <value><c>true</c> if visible; otherwise, <c>false</c>.</value>
        public override bool Visible
        {
            get
            {
                return _visible;
            }
            set
            {
                _visible = value;
                //
                _windowsFormsThreadTask.WindowsForm.Invoke(new FormInvokerDelegate(setVisibleHandler));
            }
        }

        /// <summary>
        /// Sets the visible handler.
        /// </summary>
        private void setVisibleHandler()
        {
            if (!_windowsFormsThreadTask.WindowsForm.Visible)
            {
                _windowsFormsThreadTask.WindowsForm.Visible = _visible;
            }
            GETInactivePanel().Visible = false;
            GETActivePanel().Visible = _visible;
        }

        /// <summary>
        /// Is active panel visible.
        /// </summary>
        protected override bool PlayerVisible
        {
            get
            {
                return _playerVisible;
            }
            set
            {
                _playerVisible = value;
                //
                _windowsFormsThreadTask.WindowsForm.Invoke(new FormInvokerDelegate(setPlayerVisibleHandler));
            }
        }

        /// <summary>
        /// Sets the player visible handler.
        /// </summary>
        private void setPlayerVisibleHandler()
        {
            GETInactivePanel().Visible = false;
            GETActivePanel().Visible = _playerVisible;
        }

        /// <summary>
        /// Get handle of active panel.
        /// </summary>
        /// <returns>OS specific handle of window.</returns>
        protected override IntPtr GetActiveWindowHandle()
        {
            return _firstWindowIsActive ? _windowsFormsThreadTask.Panel1Handle : _windowsFormsThreadTask.Panel2Handle;
        }

        /// <summary>
        /// Get handle of inactive panel.
        /// </summary>
        protected override IntPtr GetInactiveWindowHandle()
        {
            return _firstWindowIsActive ? _windowsFormsThreadTask.Panel2Handle : _windowsFormsThreadTask.Panel1Handle;
        }

        /// <summary>
        /// Switches active and inactive windows.
        /// </summary>
        protected override void SwitchWindows()
        {
            _firstWindowIsActive = !_firstWindowIsActive;
            _windowsFormsThreadTask.WindowsForm.Invoke(new FormInvokerDelegate(switchWindowsHandler));
        }

        /// <summary>
        /// Switches the windows handler.
        /// </summary>
        private void switchWindowsHandler()
        {
            GETInactivePanel().Visible = false;
            GETActivePanel().Visible = _playerVisible;
        }
    }
}